package com.bbs.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.PageResult;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bbs.dto.*;
import com.bbs.entity.Follow;
import com.bbs.entity.Post;
import com.bbs.mapper.PostMapper;
import com.bbs.service.FollowService;
import com.bbs.service.PostService;
import com.bbs.service.UserService;
import com.bbs.util.UserHolder;
import com.bbs.vo.PostVO;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import jakarta.annotation.Resource;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleBiFunction;
import java.util.stream.Collectors;

import static com.bbs.util.constant.*;

@Service
@Slf4j
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

//获取线程池
static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
@Resource
    UserService userService;
@Resource
    FollowService followService;
    @Autowired
    private PostMapper postMapper;

    /**
     * 定时任务设置
     * cron：使用Cron表达式。每天00:00触发
     */
    @Scheduled(cron = "0 0 0 * * ?")
    public void reportCurrentTimeWithCronExpression() {
        log.info("定时任务");
    }
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    StringRedisTemplate stringRedisTemplate;


    @Override
    public PostVO createPost(PostDTO postDTO) {
        Post post = new Post();
        UserDTO userDTO = UserHolder.getUser();
        Long userId = userDTO.getId();//帖主id
        post.setAuthorId(userId);
        BeanUtils.copyProperties(postDTO, post);
        boolean save = save(post);
        if(save){
            //如果帖子添加成功，将帖子id存储到粉丝收件箱里
            //1.获取粉丝id集合
            List<Follow> fans = followService.query().eq("follow_user_id", userId).list();
            for (Follow fan : fans) {
                Long fansId = fan.getUserId();
                String key=FEED_POST_BOX+fansId;
                stringRedisTemplate.opsForZSet().add("key",post.getId().toString(),System.currentTimeMillis());
            }
        }
        return convertToVO(post);
    }
    
    @Override
    public PostVO getPostById(Long id) {
       //先去缓存查询
        String s = stringRedisTemplate.opsForValue().get(POST_CACHE_KEY + id);
        //如果缓存里有直接返回
        if(StrUtil.isNotBlank(s)) {
            PostVO postVO = JSONUtil.toBean(s, PostVO.class);
            return postVO;
        }

        //如果缓存里是""返回没有
        if(s!=null){
            return null;
        }
        //如果缓存是null去数据库查，如果数据库没有则向redis填写缓存空对象
        Post post = getById(id);
        if(post==null){
            //向redis写缓存空对象
            stringRedisTemplate.opsForValue().set(POST_CACHE_KEY + id,"",POST_NULL_TIME,TimeUnit.MINUTES);
            return null;
        }
        //添加缓存
        PostVO postVO = convertToVO(post);
        String jsonStr = JSONUtil.toJsonStr(postVO);
        //添加缓存
        stringRedisTemplate.opsForValue().set(POST_CACHE_KEY + id,jsonStr,POST_CACHE_TIME,TimeUnit.HOURS);
        return postVO;
    }
    
    @Override
    public List<PostVO> getAllPosts() {
        List<Post> posts = list();
        return posts.stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
    }


    @Override
    @Transactional
    public PostVO updatePost(Long id, PostDTO postDTO) {

        Long postId = postDTO.getId();

        // 获取锁,获取成功，通过事务操作，进行更新帖子以及删除缓存操作

        // 根据帖子的id作为锁标识(更新锁)
       String key=LOCK_UPDATE_POST+id;
       //获取对应的锁
        RLock lock = redissonClient.getLock(key);
        int retryCount=1;
        int MAX_RETRY_COUNT=3;
while(retryCount<=MAX_RETRY_COUNT)
{
    //尝试获取锁
    try{
        boolean flag = lock.tryLock(2,30,TimeUnit.SECONDS);
        if(!flag){
        //
            Thread.sleep(50);//休息会
            retryCount++;
            continue;
        }
        //获取锁成功
        try{
            //
            Post post = getById(id);
            BeanUtils.copyProperties(postDTO, post);
            updateById(post);
            //删除缓存
            stringRedisTemplate.delete(POST_CACHE_KEY+id);
            //延迟删除实现延时双删
            executor.schedule(()->{
                stringRedisTemplate.delete(POST_CACHE_KEY+id);
            },500,TimeUnit.MILLISECONDS);
            return convertToVO(post);
        }catch (Exception e){
            throw new Exception();
        }
        finally {
            boolean isSelfLock = lock.isHeldByCurrentThread();
            if(isSelfLock)lock.unlock();
            //释放锁，首先判断锁是不是自己的
        }
    }catch (Exception e){
        throw new RuntimeException("更新时出现异常，请稍后再试");
    }
}
throw new RuntimeException("系统繁忙，请稍后再试");
    }
    
    @Override
    public void deletePost(Long id) {
        removeById(id);
    }
    
    @Override
    public List<PostVO> getPostsByUserId(Long userId) {
        LambdaQueryWrapper<Post> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Post::getAuthorId, userId);
        List<Post> posts = list(wrapper);
        return posts.stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
    }
    
    @Override
    public void incrementViewCount(Long id) {
        Post post = getById(id);
        post.setViewCount(post.getViewCount() + 1);
        updateById(post);
    }

    @Override
    public Result getTOP5LikeByPostId(Long id) {
        //去缓存查询前五条信息,
        //zrange key 0 4
        String key=LIKE_CACHE_KEY+id;
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if(top5==null||top5.isEmpty()){
            //返回空集合
            return Result.ok(Collections.emptyList());
        }
        log.info("redis查询集合:{}",top5);
        //通过集合映射(T->R)操作将string id转化为Long id
        List<Long> list = top5.stream().map(Long::valueOf).toList();
        //根据id查询用户
        String join = StrUtil.join(",",list);
        List<UserDTO> userDTOList = userService.query().in("id", list)
                //.orderBy(true, true, "id")'.
                .last("order by "+"field(id,"+join+")")
                .list().stream().map(user ->
                        BeanUtil.copyProperties(user, UserDTO.class)
                ).collect(Collectors.toList());
        log.info("用户集合:{}",userDTOList);
        return Result.ok(userDTOList);
    }
//TODO 分页查询
    @Override
    public MyPageResult<PostVO> pageQuery(PostPageQueryDTO postPageQueryDTO) {
        int pageSize = postPageQueryDTO.getPageSize();
        int page = postPageQueryDTO.getPage();
        PageHelper.startPage(page,pageSize);
      Page<Post> pageResult=  postMapper.queryPage(postPageQueryDTO);
        long total = pageResult.getTotal();
        List<Post> result = pageResult.getResult();
        List<PostVO> postVOList= result.stream().map(this::convertToVO).collect(Collectors.toList());
return new MyPageResult<PostVO>(postVOList,pageSize,page,total);
    }
//TODO 仅为测试LambdaUpdate
    @Override
    public Result updateViewCount(Long id, Integer num) {
        Post post = query().eq("id", id).one();
        if(post==null)return Result.fail("帖子不存在");
        Integer viewCount = post.getViewCount();
        if(viewCount<num)return Result.fail("帖子浏览数低于待扣除数");
        Integer curViewCount=viewCount-num;
        lambdaUpdate().set(Post::getViewCount,curViewCount)
                .eq(Post::getId,id)
                .update();
        return Result.ok();
    }


    private PostVO convertToVO(Post post) {
        if (post == null) {
            return null;
        }
        PostVO vo = new PostVO();
        BeanUtils.copyProperties(post, vo);
        return vo;
    }
//TODO该方法实现上具有漏洞，主要是学习运用ZSet实现更新推送功能
    @Override
    public Result queryPostOfFollow(Long max, Integer offset) {
       //获取用户id
        Long userId = UserHolder.getUser().getId();
        String key=FEED_KEY+userId;


        //public interface TypedTuple<V> extends Comparable<TypedTuple<V>> {
        //        @Nullable
        //        V getValue();
        //
        //        @Nullable
        //        Double getScore();
        //
        //        static <V> TypedTuple<V> of(V value, @Nullable Double score) {
        //            return new DefaultTypedTuple(value, score);
        //        }
        //    }
        //去redis查询 ZREVRANGEBYSCORE key max min limit offset(跳过几个) 3
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, FEED_COUNT);//key,最小值，最大值，偏移量,查询数量
//根据帖子id查询帖子

        //返回
        return Result.ok();
    }

} 